#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File    : interface.window.py
# @Note    : GUI 主窗口界面

import ctypes
import os
import time
from shutil import move
from winreg import OpenKey, QueryValueEx, HKEY_CURRENT_USER

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import psutil
import pythoncom
import win32con
from PyQt5.QtCore import QThread, QTimer, Qt, pyqtSignal
from PyQt5.QtGui import QIcon, QPixmap, QTextCursor, QFont
from PyQt5.QtWidgets import QMainWindow, QFileDialog, QApplication, QMessageBox, QGridLayout
from PyQt5.QtWidgets import QSizePolicy
from PyQt5.uic import loadUiType
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from win32process import SuspendThread, ResumeThread

from interface import version, contact, waiting, accelerate, model, vissim_setup
from simulation import simulation

ui, _ = loadUiType("./resource/ui/window.ui")

demo = False

class MyThread(QThread):
    handle = -1
    stop_flag = False
    save_signal = pyqtSignal(bool)

    def __init__(self):
        super().__init__()
        self.sim = simulation.Sim()
        self.para = []

    def run(self):
        try:
            self.handle = ctypes.windll.kernel32.OpenThread(win32con.PROCESS_ALL_ACCESS,
                                                            False, int(QThread.currentThreadId()))
        except (IndexError, Exception):
            pass
        if not self.stop_flag:
            # 初始化资源
            pythoncom.CoInitialize()
            self.sim.run(self.para[0], self.para[1], self.para[2])
            print("仿真结束")
            self.save_signal.emit(True)
        else:
            pass


class PlotCanvas(FigureCanvas):
    def __init__(self, parent=None, width=6, height=4, dpi=100):
        self.fig = Figure(figsize=(width, height), dpi=dpi)
        FigureCanvas.__init__(self, self.fig)
        font = {'family': 'SimSun', 'size': '8'}
        plt.rc('font', **font)
        plt.rc('axes', unicode_minus=False)
        plt.rcParams['figure.facecolor'] = "#FFFFF0"  # 设置窗体颜色
        plt.rcParams['axes.facecolor'] = "#FFFFF0"  # 设置绘图区颜色
        plt.rcParams['font.family'] = 'Times New Roman'  # 设置字体样式
        plt.rcParams['font.size'] = '8'  # 设置字体大小
        self.setParent(parent)
        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

    def verify_path(self):
        if demo:
            self.train_drl_file = './test/model/txt/train_record.txt'
            self.train_test_drl_file = './test/model/txt/test_record.txt'
            self.test_drl_file = "./test/model/txt/test_drl_record.txt"
            self.test_fix_file = "./test/fix/txt/test_fix_record.txt"
        else:
            self.train_drl_file = './model/txt/train_record.txt'
            self.train_test_drl_file = './model/txt/test_record.txt'
            self.test_drl_file = "./model/txt/test_drl_record.txt"
            self.test_fix_file = "./fix/txt/test_fix_record.txt"

    def clear_fig(self):
        self.fig.clf()  # 清理画布
        # 重绘画布
        self.fig.canvas.draw()
        # 更新画布
        self.fig.canvas.flush_events()

    def plot_drl_train(self, pic):
        self.verify_path()
        # 读取drl训练数据
        names = ["episode", "step", "epsilon", "learn_rate", "convergence", "delay", "reward", "loss"]
        data = pd.read_csv(self.train_drl_file, sep="\s+", names=names)
        delay = list(data["delay"].values)
        reward = list(data["reward"].values)
        loss = list(data["loss"].values)
        convergence = list(data["convergence"].values)
        epsilon = list(data["epsilon"].values)
        learn_rate = list(data["learn_rate"].values)
        idx = 0
        for i in range(len(loss)):
            if loss[i] != 0:
                idx = i
                break
        loss = loss[idx::]
        # 读取drl训练测试数据
        names = ["episode", "step", "delay", "reward"]
        data = pd.read_csv(self.train_test_drl_file, sep="\s+", names=names)
        train_test_delay = list(data["delay"].values)
        train_test_reward = list(data["reward"].values)
        y = [delay, reward, loss, epsilon, learn_rate, convergence, train_test_delay, train_test_reward]
        pic = np.array(pic)
        num = len(pic[pic!=0])
        n = 0
        self.fig.clf() # 清理画布
        for i in range(len(pic)):
            if pic[i] != 0:
                n += 1
                ax = self.fig.add_subplot(1, num, n)
                x = np.linspace(0, len(y[i]), len(y[i]))
                ax.plot(x, y[i], "k")
        # 重绘画布
        self.fig.canvas.draw()
        # 更新画布
        self.fig.canvas.flush_events()

    def plot_drl_test(self, pic):
        self.verify_path()
        # 读取drl配时测试数据
        names = ["step", "plan", "delay", "reward"]
        data = pd.read_csv(self.test_drl_file, sep="\s+", names=names)
        # drl延误
        drl_delay = list(data["delay"].values)
        drl_plan = list(data["plan"].values)
        # drl平均延误
        mean_delay = round(sum(drl_delay) / len(drl_delay), 3)
        # 读取固定配时测试数据
        names = ["plan", "step", "delay"]
        data = pd.read_csv(self.test_fix_file, sep="\s+", names=names)
        fix_plan = list(data["plan"].values)
        # 固定延误
        fix_delay = list(data["delay"].values)
        compar = list(map(lambda x: round((x - mean_delay) / x * 100, 2), fix_delay))
        pic = np.array(pic)
        num = len(pic[pic != 0])
        n = 0
        self.fig.clf()  # 清理画布
        for i in range(len(pic)):
            if pic[i] != 0:
                n += 1
                ax = self.fig.add_subplot(1, num, n)
                if i == 0:
                    x = np.linspace(0, len(drl_delay) - 1, len(drl_delay))
                    ax.plot(x, drl_delay, color='black', marker='D', linestyle='-', linewidth='1.0')
                    ax.plot(x, [mean_delay for _ in range(len(x))], color='gray', linestyle='--')
                elif i == 1:
                    medianprops = dict(linestyle='-', linewidth=2.5, color='black')
                    meanpointprops = dict(marker='D', markeredgecolor='black', markerfacecolor='gray')
                    ax.boxplot(drl_delay, vert=False, showmeans=True, medianprops=medianprops, meanprops=meanpointprops)
                elif i == 2:
                    x = np.linspace(0, len(drl_plan) - 1, len(drl_plan))
                    ax.scatter(x, drl_plan, color='black', s=20, marker='o')
                elif i == 3:
                    se = pd.Series(drl_plan)
                    plan_num = len(fix_plan)
                    proportitionDict = dict(se.value_counts(normalize=True))
                    plan_freq = []
                    for i in range(plan_num):
                        try:
                            plan_freq.append(proportitionDict[i])
                        except:
                            plan_freq.append(0)
                    x = np.linspace(0, len(plan_freq) - 1, len(plan_freq))
                    ax.bar(x, plan_freq, 0.5, color="gray", edgecolor="k")
                elif i == 4:
                    x = np.linspace(0, len(compar) - 1, len(compar))
                    ax.plot(x, fix_delay, color='black', marker='D', linestyle='-')
                    ax.plot(x, [mean_delay for _ in range(len(fix_plan))], color='gray', marker='*', linestyle='--')
                elif i == 5:
                    # 指定固定配时方案延误
                    specify_plan = pic[i]
                    specify_fix_file = self.test_fix_file[:-4] + '_' + str(specify_plan) + '.txt'
                    names = ["step", "delay"]
                    data = pd.read_csv(specify_fix_file, sep="\t+", names=names, engine="python")
                    fix_specify_delay = list(data["delay"].values)
                    fix_specify_mean_delay = round(sum(fix_specify_delay) / len(fix_specify_delay), 3)
                    x = np.linspace(0, len(fix_specify_delay) - 1, len(fix_specify_delay))
                    ax.plot(x, fix_specify_delay, color='gray', marker='o', linestyle=':')
                    ax.plot(x, drl_delay, color='black', marker='D', linestyle='--')
                    ax.plot(x, [fix_specify_mean_delay for _ in range(len(fix_specify_delay))], color='gray', marker='o',
                             linewidth=0.5,
                             linestyle='--')
                    ax.plot(x, [mean_delay for _ in range(len(drl_delay))], color='black', marker='D',
                             linewidth=0.5,
                             linestyle='--')
        # 重绘画布
        self.fig.canvas.draw()
        # 更新画布
        self.fig.canvas.flush_events()

    def plot_fix_test(self, pic):
        self.verify_path()
        # 读取固定配时测试数据
        names = ["plan", "step", "delay"]
        data = pd.read_csv(self.test_fix_file, sep="\s+", names=names)
        fix_plan = list(data["plan"].values)
        fix_delay = list(data["delay"].values)
        min_delay = min(filter(lambda x: x > 0, fix_delay))
        fix_min_idx = fix_plan[fix_delay.index(min_delay)]
        fix_max_idx = fix_plan[fix_delay.index(max(fix_delay))]
        # min-fix 延误
        min_fix_file = self.test_fix_file[:-4] + '_' + str(fix_min_idx) + '.txt'
        names = ["step", "delay"]
        data = pd.read_csv(min_fix_file, sep="\t+", names=names, engine="python")
        fix_min_delay = list(data["delay"].values)
        pic = np.array(pic)
        num = len(pic[pic != 0])
        n = 0
        self.fig.clf()  # 清理画布
        for i in range(len(pic)):
            if pic[i] != 0:
                n += 1
                ax = self.fig.add_subplot(1, num, n)
                if i == 0:
                    x = np.linspace(1, len(fix_delay), len(fix_delay))
                    ax.bar(fix_min_idx, min_delay, 0.5, label='min delay plan', color="w", edgecolor="k",
                            hatch="\\\\\\\\")
                    ax.bar(fix_max_idx, max(fix_delay), 0.5, label='max delay plan', color="w", edgecolor="k",
                            hatch="/////")
                    ax.plot(x, [np.mean(fix_delay) for _ in range(len(x))], label='mean delay line', color='gray',
                             linestyle='--')
                elif i == 1:
                    x = np.linspace(1, len(fix_min_delay), len(fix_min_delay))
                    mean_delay = round(sum(fix_min_delay) / len(fix_min_delay), 3)
                    ax.plot(x, fix_min_delay, label='min delay', color='black', linestyle='--')
                    ax.plot(x, [mean_delay for _ in range(len(fix_min_delay))], color='black', linestyle='--')
                elif i == 2:
                    # 指定固定配时方案延误
                    specify_plan = pic[i]
                    specify_fix_file = self.test_fix_file[:-4] + '_' + str(specify_plan) + '.txt'
                    names = ["step", "delay"]
                    data = pd.read_csv(specify_fix_file, sep="\t+", names=names, engine="python")
                    fix_specify_delay = list(data["delay"].values)
                    mean_delay = round(sum(fix_specify_delay) / len(fix_specify_delay), 3)
                    x = np.linspace(0, len(fix_specify_delay) - 1, len(fix_specify_delay))
                    ax.plot(x, fix_specify_delay, color='black', marker='o', linestyle=':')
                    ax.plot(x, [mean_delay for _ in range(len(fix_specify_delay))], color='black',linestyle='--')
        # 重绘画布
        self.fig.canvas.draw()
        # 更新画布
        self.fig.canvas.flush_events()


# 主窗口界面
class MainWindow(QMainWindow, ui):
    SimHintInfoEmit = pyqtSignal(int)
    SimModelInfoEmit = pyqtSignal(int)
    SimNetworkInfoEmit = pyqtSignal(str)
    SimStrategyInfoEmit = pyqtSignal(list)
    SimRewardInfoEmit = pyqtSignal(int)

    def __init__(self):
        QMainWindow.__init__(self)
        self.setupUi(self)
        self.resize(1366, 700)
        self.setWindowFlags(Qt.WindowMinimizeButtonHint)
        # DESIGN OF DRL TIMING SIMULATION SYSTEM FOR SIGNAL INTERSECTION INTEGRATING VISSIM-PYTHON AND QT
        self.setWindowTitle("集成Vissim-Python和Qt的信控交叉口DRL配时仿真系统设计")
        self.setWindowTitle(
            "Design of DRL timing simulation system for signal intersection integrating Vissim-Python and Qt")
        self.setWindowIcon(QIcon("./resource/icon/app.png"))
        # 隐藏tab标签
        self.tabWidget.tabBar().setVisible(False)
        # 类属性
        self.program_path = r"E:\Program Files (x86)\PTV_Vision\VISSIM430\Exe\vissim.exe"
        self.net_path = os.getcwd().replace("\\", "/") + "/resource/vissim/net/net.inp"
        self.flow_path = os.getcwd().replace("\\", "/") + "/resource/data/vehicle_inputs.txt"
        self.save_path = os.getcwd().replace("\\", "/") + "/resource/save/"
        self.vehicle = []
        self.num = 0
        self.simulation = [999999, 0, 1, 1, 42, False, False]
        self.timming = [170, 41, 57, 2, 3, [2, 2, 2]]
        self.plans = []
        self.start_time = r"01\01\2008 00:04:10"
        self.pic1 = [0 for _ in range(8)]
        self.pic2 = [0 for _ in range(6)]
        self.pic3 = [0 for _ in range(4)]
        # 实例化类
        self.versionDialog = version.versionDialog()
        self.contactDialog = contact.contactDialog()
        self.vissim_setupDialog = vissim_setup.vissim_setupDialog()
        self.accelerateDialog = accelerate.accelerateDialog()
        self.modelDialog = model.modelDialog()
        self.waiting = waiting.wait_start()
        # 让图片自适应label大小
        self.label_net.setScaledContents(True)
        self.label_alg.setScaledContents(True)
        self.label_sim.setScaledContents(True)
        self.label_eval.setScaledContents(True)
        # 设置图标
        self.label_net.setPixmap(QPixmap("./resource/icon/net.png"))
        self.label_alg.setPixmap(QPixmap("./resource/icon/algorithm.png"))
        self.label_sim.setPixmap(QPixmap("./resource/icon/simulation.png"))
        self.label_eval.setPixmap(QPixmap("./resource/icon/evaluate.png"))
        # 主界面界面切换槽函数
        self.pushButton1.clicked.connect(self.switch_to_net_panel)
        self.pushButton2.clicked.connect(self.switch_to_algorithm_panel)
        self.pushButton3.clicked.connect(self.switch_to_simulation_panel)
        self.pushButton4.clicked.connect(self.switch_to_evaluate_panel)
        # 菜单栏触发信号
        self.action_load.triggered.connect(self.load_net_file)
        self.action_save.triggered.connect(self.save_net_file)
        self.action_exit.triggered.connect(self.quit_program)
        self.action_vissim_setup.triggered.connect(self.vissim_setupDialog.show)
        self.action_accelerate.triggered.connect(self.accelerateDialog.show)
        self.action_model.triggered.connect(self.modelDialog.show)
        self.action_local.triggered.connect(self.open_local_help_document)
        self.action_version.triggered.connect(self.versionDialog.show)
        self.action_contact.triggered.connect(self.contactDialog.show)
        # 路网界面按钮槽连接
        self.pushButton_load.clicked.connect(self.load_net_file)
        self.pushButton_parse.clicked.connect(self.parse_net_file)
        self.pushButton_save.clicked.connect(self.save_net_file)
        # 算法界面槽连接
        self.comboBox_algorithm.currentIndexChanged.connect(self.load_algorithm)
        # 仿真界面按钮槽连接
        self.pushButton_script_path.clicked.connect(self.load_script_path)
        self.pushButton_simulation_run.clicked.connect(self.run_simulation)
        self.pushButton_simulation_stop_continue.clicked.connect(self.stop_continue_simulation)
        self.pushButton_simulation_quit.clicked.connect(self.quit_simulation)
        # 仿真信息提示
        self.radioButton_no_information.toggled.connect(lambda checked: self.simulation_hint_type_setting(checked))
        self.radioButton_step_information.toggled.connect(lambda checked: self.simulation_hint_type_setting(checked))
        self.radioButton_episode_information.toggled.connect(lambda checked: self.simulation_hint_type_setting(checked))
        # 仿真运行模式
        self.radioButton_test_fix.toggled.connect(lambda checked: self.simulation_run_model_setting(checked))
        self.radioButton_train_drl.toggled.connect(lambda checked: self.simulation_run_model_setting(checked))
        self.radioButton_test_drl.toggled.connect(lambda checked: self.simulation_run_model_setting(checked))
        # 算法策略、奖励选择
        self.radioButton_ucb.toggled.connect(lambda checked: self.load_strategy_setting(checked))
        self.radioButton_greedy.toggled.connect(lambda checked: self.load_strategy_setting(checked))
        self.radioButton_softmax.toggled.connect(lambda checked: self.load_strategy_setting(checked))
        self.radioButton_damping_linear.toggled.connect(lambda checked: self.load_strategy_setting(checked))
        self.radioButton_damping_exp.toggled.connect(lambda checked: self.load_strategy_setting(checked))
        self.radioButton_damping_cos.toggled.connect(lambda checked: self.load_strategy_setting(checked))
        self.radioButton_reward1.toggled.connect(lambda checked: self.load_reward_setting(checked))
        self.radioButton_reward2.toggled.connect(lambda checked: self.load_reward_setting(checked))
        self.radioButton_reward3.toggled.connect(lambda checked: self.load_reward_setting(checked))
        # 评估界面图片展示选择
        self.checkBox_train_delay.toggled.connect(self.select_train_drl_display)
        self.checkBox_train_reward.toggled.connect(self.select_train_drl_display)
        self.checkBox_train_loss.toggled.connect(self.select_train_drl_display)
        self.checkBox_train_epsilon.toggled.connect(self.select_train_drl_display)
        self.checkBox_train_lr.toggled.connect(self.select_train_drl_display)
        self.checkBox_train_test_delay.toggled.connect(self.select_train_drl_display)
        self.checkBox_train_test_reward.toggled.connect(self.select_train_drl_display)
        self.checkBox_train_conv.toggled.connect(self.select_train_drl_display)
        self.checkBox_demo.toggled.connect(self.switch_demo_model)
        # ----
        self.checkBox_test_delay.toggled.connect(self.select_test_drl_display)
        self.checkBox_delay_box.toggled.connect(self.select_test_drl_display)
        self.checkBox_plan_scatter.toggled.connect(self.select_test_drl_display)
        self.checkBox_plan_freq.toggled.connect(self.select_test_drl_display)
        self.checkBox_compare_all_fix.toggled.connect(self.select_test_drl_display)
        self.checkBox_compare_specify_fix.toggled.connect(self.select_test_drl_display)
        self.spinBox_plan.valueChanged.connect(self.select_test_drl_display)
        # ----
        self.checkBox_fix_all_test_delay.toggled.connect(self.select_test_fix_display)
        self.checkBox_fix_min_test_delay.toggled.connect(self.select_test_fix_display)
        self.checkBox_specify_fix.toggled.connect(self.select_test_fix_display)
        self.spinBox_plan_fix.valueChanged.connect(self.select_test_fix_display)
        # 样式表切换
        self.QSS_sheet = [
            "QPushButton{border:none;color:black;font-size:11;border-radius:10px;padding-left:5px;padding-right:10px;\
            text-align:middle;background:LightGray;background-color:#FF69B4;}"
            "QPushButton:hover{color:white;border:1px solid #F3F3F5;border-radius:10px;background:#00FF00;}"
            "QPushButton:pressed{color:red;border:3px solid #4169E1;border-radius:10px;background:#00FF00;}",
            "QPushButton{border:none;color:black;font-size:11;border-radius:10px;padding-left:5px;padding-right:10px;\
            text-align:middle;background:LightGray;background-color:#808080;}"
            "QPushButton:hover{color:white;border:1px solid #F3F3F5;border-radius:10px;background:#00FF00;}"
            "QPushButton:pressed{color:red;border:3px solid #4169E1;border-radius:10px;background:#00FF00;}",
        ]
        # 按钮状态
        self.pushButton_simulation_quit.setEnabled(False)
        self.pushButton_simulation_quit.setStyleSheet(self.QSS_sheet[1])
        self.pushButton_simulation_stop_continue.setEnabled(False)
        self.pushButton_simulation_stop_continue.setStyleSheet(self.QSS_sheet[1])
        self.pushButton_simulation_stop_continue.setText("停止仿真")
        # 注册子线程服务
        self.sim_task = MyThread()
        self.sim = self.sim_task.sim
        self.sim_task.finished.connect(self.sim_task.deleteLater)
        # self.sim_task.save_signal.connect(self.save_simulation_data)
        # 连接自定义信号
        self.sim.SimInfoEmit.connect(self.update_simulation_textBrowser)
        self.sim.EvaInfoEmit.connect(self.update_figure)
        self.sim.RateProgressInfoEmit.connect(self.update_simulation_progressBar)
        self.sim.RemainTimeInfoEmit.connect(self.update_simulation_time)
        self.SimHintInfoEmit.connect(self.sim.get_simulation_hint)
        self.SimModelInfoEmit.connect(self.sim.get_simulation_model)
        self.SimNetworkInfoEmit.connect(self.sim.get_simulation_test_network)
        self.SimStrategyInfoEmit.connect(self.sim.get_simulation_strategy)
        self.SimRewardInfoEmit.connect(self.sim.get_simulation_reward)
        # 系统状态刷新定时器
        self.system_state = QTimer()
        self.system_state.timeout.connect(self.imshow_system_state)
        self.system_state.stop()
        # 可视化评估信息定时器
        self.visual_evaluation = QTimer()
        self.visual_evaluation.timeout.connect(self.update_figure)
        self.visual_evaluation.stop()
        # 绘图区域
        self.F = PlotCanvas()
        self.F1 = PlotCanvas(width=4, height=5, dpi=30)
        self.F2 = PlotCanvas(width=4, height=5, dpi=30)
        self.F3 = PlotCanvas(width=4, height=5, dpi=30)
        self.gridlayout_1 = QGridLayout(self.groupBox_plot_1)
        self.gridlayout_2 = QGridLayout(self.groupBox_plot_2)
        self.gridlayout_3 = QGridLayout(self.groupBox_plot_3)
        self.gridlayout_1.addWidget(self.F1, 0, 1)
        self.gridlayout_2.addWidget(self.F2, 0, 1)
        self.gridlayout_3.addWidget(self.F3, 0, 1)
        # 初始进度条信息
        self.progressBar_memory.setValue(0)
        self.progressBar_cpu.setValue(0)
        self.progressBar_gpu.setValue(0)
        self.progressBar_simulation.setValue(0)
        # 显示系统状态信息
        self.imshow_system_state()
        # 默认显示算法界面
        self.switch_to_algorithm_panel()

    # 切换演示模式
    def switch_demo_model(self):
        global demo
        if self.checkBox_demo.isChecked():
            demo = True
        else:
            demo = False
        self.F1.clear_fig()
        self.F2.clear_fig()
        self.F3.clear_fig()
        self.update_figure()

    # 获取桌面路径
    @staticmethod
    def get_desktop_path():
        key = OpenKey(HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders')
        return QueryValueEx(key, "Desktop")[0]

    # 备份网络模型
    @staticmethod
    def backup_model(source, target):
        # 移动所有模型文件到备份文件夹下
        for root, dirs, files in os.walk(source):
            if len(files) > 0:
                # 获取系统时间
                now = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))
                # 创建新备份子文件夹
                path = target + now + '/'
                if os.path.exists(path):
                    pass
                else:
                    os.makedirs(path)
                # 移动文件
                for file in files:
                    move(os.path.join(root, file), path)

    # 创建配时方案
    @staticmethod
    def create_plans(para, file, plan_num=-1):
        # 配时范围
        cycle_time = para[0]
        green_low = para[1]
        green_high = para[2]
        green_interval = para[3]
        amber_time = para[4]
        clearing_time = para[5]
        phase_num = len(clearing_time)
        loss_time = amber_time * phase_num + sum(clearing_time)
        try:
            with open(file, "a+") as f:
                f.truncate(0)
        except (IndexError, Exception):
            pass
        from itertools import permutations
        a = list(permutations(range(green_low, green_high + 1, green_interval), phase_num))
        plans = []
        unused_plan = 0
        for i in range(len(a)):
            if sum(a[i]) == cycle_time - loss_time:
                if plan_num < 0 or len(plans) < plan_num:
                    plan = [cycle_time, amber_time, clearing_time, list(a[i])]
                    plans.append(plan)
                    with open(file, "a+") as f:
                        line = "%s\n" % (str(plan))
                        f.write(line)
                else:
                    unused_plan += 1
        return plans, unused_plan

    # 显示系统状态信息
    def imshow_system_state(self):
        # 内存占用
        mem = psutil.virtual_memory()
        mem_percent = int(mem.percent)
        self.progressBar_memory.setValue(mem_percent)
        self.progressBar_memory.update()
        # CPU占用
        cpu_percent = psutil.cpu_percent(1)
        cpu_percent = int(cpu_percent)
        self.progressBar_cpu.setValue(cpu_percent)
        self.progressBar_cpu.update()
        # GPU占用
        import GPUtil
        GPUs = GPUtil.getGPUs()
        gpu_load = GPUs[0].load
        gpu_percent = gpu_load * 100
        gpu_percent = int(gpu_percent)
        self.progressBar_gpu.setValue(gpu_percent)
        self.progressBar_gpu.update()

    # 退出程序
    def quit_program(self):
        reply = QMessageBox.question(self, '提示', "是否确认退出仿真系统？", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.close()
        else:
            self.showMinimized()

    # 打开本地帮助文档
    def open_local_help_document(self):
        path = os.getcwd() + r"/resource/help/user manual.chm"
        if os.path.exists(path):
            os.startfile(path)
        else:
            QMessageBox.warning(self, "提示", "当前目录下帮助文档不存在")

    # 切换到路网界面
    def switch_to_net_panel(self):
        self.tabWidget.setCurrentIndex(0)
        self.statusBar().showMessage("路网界面")
        self.system_state.stop()
        self.visual_evaluation.stop()

    # 切换到算法界面
    def switch_to_algorithm_panel(self):
        self.tabWidget.setCurrentIndex(1)
        self.statusBar().showMessage("算法界面")
        self.system_state.stop()
        self.visual_evaluation.stop()

    # 切换到仿真界面·
    def switch_to_simulation_panel(self):
        self.tabWidget.setCurrentIndex(2)
        self.statusBar().showMessage("仿真界面")
        self.imshow_system_state()
        self.visual_evaluation.stop()
        self.system_state.start(10 * 1000)

    # 切换到评价界面
    def switch_to_evaluate_panel(self):
        self.tabWidget.setCurrentIndex(3)
        self.statusBar().showMessage("评估界面")
        self.switch_demo_model()
        self.select_train_drl_display()
        self.select_test_drl_display()
        self.select_test_fix_display()
        self.system_state.stop()
        self.visual_evaluation.start(10 * 1000)

    """路网界面"""

    # 加载路网文件
    def load_net_file(self):
        reply = QFileDialog.getOpenFileName(self, "道路仿真路网文件", "./resource/", "(*.inp)")
        try:
            if reply[0]:
                self.net_path = reply[0]
                try:
                    with open(self.net_path, "r") as f:
                        content = f.readlines()
                    self.textBrowser_net_info.clear()
                    for i in range(len(content)):
                        self.textBrowser_net_info.append(content[i])
                    self.textBrowser_net_info.moveCursor(QTextCursor.Start)
                except (IndexError, Exception):
                    QMessageBox.warning(self, "提示", "加载路网文件失败")
                QMessageBox.information(self, "提示", "加载路网文件成功")
            else:
                QMessageBox.warning(self, "提示", "请选择正确的路径并确认")
        except (IndexError, Exception):
            QMessageBox.warning(self, "提示", "加载路网文件失败")

    # 解析路网文件
    def parse_net_file(self):
        try:
            with open(self.net_path, "r") as f:
                content = f.readlines()
            self.textBrowser_parse_info.clear()
            for i in range(len(content)):
                self.textBrowser_parse_info.append(content[i])
            self.textBrowser_parse_info.moveCursor(QTextCursor.Start)
            QMessageBox.information(self, "提示", "解析路网文件成功")
        except (IndexError, Exception):
            QMessageBox.warning(self, "提示", "解析路网文件失败")

    # 保存路网文件
    def save_net_file(self):
        reply = QFileDialog.getSaveFileName(self, '道路仿真路网文件', './resource/', "文本(*.inp)")
        try:
            if reply[0]:
                with open(reply[0], "w") as f:
                    f.write(self.textBrowser_net_info.toPlainText())
                QMessageBox.information(self, "提示", "保存路网文件成功")
            else:
                QMessageBox.warning(self, "提示", "请选择正确的路径并确认")
        except (IndexError, Exception):
            QMessageBox.warning(self, "提示", "保存路网文件失败")

    """算法界面"""

    # 加载算法脚本文件
    def load_script_path(self):
        reply = QFileDialog.getOpenFileName(self, "算法脚本文件", "./script/", "(*.py)")
        try:
            if reply[0]:
                self.script_path = reply[0]
                QMessageBox.information(self, "提示", "深度强化学习配时算法脚本加载成功")
            else:
                QMessageBox.information(self, "提示", "深度强化学习配时算法脚本加载失败")
        except (IndexError, Exception):
            QMessageBox.information(self, "提示", "深度强化学习配时算法脚本加载失败")

    # 加载已有算法库算法名称
    def load_algorithm(self):
        ALGORITHM = self.comboBox_algorithm.currentText()
        print(ALGORITHM)

    # 加载仿真训练参数设置
    def load_train_setting(self):
        simulation.EPISODE = int(self.lineEdit_EPISODE.text())
        simulation.STEP = int(self.lineEdit_STEP.text())
        simulation.WARM_STEP = int(self.lineEdit_WARM_STEP.text())
        simulation.TEST_EPISODE = float(self.lineEdit_TEST_EPISODE.text())
        simulation.CONVERGENCE_LOW = int(self.lineEdit_CONVERGENCE_LOW.text())
        simulation.CONVERGENCE_UP = int(self.lineEdit_CONVERGENCE_UP.text())
        simulation.ZEROREWARD = int(self.lineEdit_ZEROREWARD.text())
        simulation.CONVERGENCE = int(self.lineEdit_CONVERGENCE.text())

    # 加载仿真算法参数设置
    def load_algorithm_setting(self):
        simulation.LR_MAX = float(self.lineEdit_LR_MAX.text())
        simulation.LR_MIN = float(self.lineEdit_LR_MIN.text())
        simulation.EPSILON_MAX = float(self.lineEdit_EPSILON_MAX.text())
        simulation.EPSILON_MIN = float(self.lineEdit_EPSILON_MIN.text())
        simulation.MEMORY_CAPACITY = int(self.lineEdit_MEMORY_CAPACITY.text())
        simulation.BATCH_SIZE = int(self.lineEdit_BATCH_SIZE.text())
        simulation.UPDATE_STEP = int(self.lineEdit_UPDATE_STEP.text())
        simulation.GAMMA = float(self.lineEdit_GAMMA.text())

    # 加载仿真算法策略参数设置
    def load_strategy_setting(self, checked):
        if checked:
            if self.radioButton_ucb.isChecked():
                strategy = 1
            elif self.radioButton_greedy.isChecked():
                strategy = 2
            elif self.radioButton_softmax.isChecked():
                strategy = 3
            else:
                strategy = 2
            if self.radioButton_damping_linear.isChecked():
                damping = 1
            elif self.radioButton_damping_exp.isChecked():
                damping = 2
            elif self.radioButton_damping_cos.isChecked():
                damping = 3
            else:
                damping = 3
            self.SimStrategyInfoEmit.emit([strategy, damping])

    # 加载仿真算法奖励参数设置
    def load_reward_setting(self, checked):
        if checked:
            if self.radioButton_reward1.isChecked():
                reward = 1
            elif self.radioButton_reward2.isChecked():
                reward = 2
            elif self.radioButton_reward3.isChecked():
                reward = 3
            else:
                reward = 3
            self.SimRewardInfoEmit.emit(reward)

    """仿真界面"""

    # 仿真提示信息类型
    def simulation_hint_type_setting(self, checked):
        if checked:
            if self.radioButton_episode_information.isChecked():
                hint_type = 1
            elif self.radioButton_step_information.isChecked():
                hint_type = 2
            elif self.radioButton_no_information.isChecked():
                hint_type = 3
            else:
                hint_type = 1
            self.SimHintInfoEmit.emit(hint_type)

    # 加载仿真运行模式
    def simulation_run_model_setting(self, checked):
        if checked:
            if self.radioButton_test_fix.isChecked():
                run_model = 1
            elif self.radioButton_train_drl.isChecked():
                run_model = 2
            elif self.radioButton_test_drl.isChecked():
                run_model = 3
                reply = QFileDialog.getOpenFileName(self, "加载最优网络模型文件", "./test/model/pkl/", "(*.pkl)")
                try:
                    if reply[0]:
                        self.textBrowser_sim_info.clear()
                        self.textBrowser_sim_info.append("加载深度强化配时测试网络模型路径:\n%s\n" % reply[0])
                        self.SimNetworkInfoEmit.emit(reply[0])
                        QMessageBox.information(self, "提示", "加载最优网络模型文件成功")
                    else:
                        QMessageBox.warning(self, "提示", "请选择正确的路径并确认")
                except (IndexError, Exception):
                    QMessageBox.warning(self, "提示", "加载最优网络模型文件失败")
            else:
                run_model = 2
            self.SimModelInfoEmit.emit(run_model)

    # 显示提示信息
    def show_hint_information(self):
        self.textBrowser_sim_info.clear()
        self.textBrowser_sim_info.append("Real-time simulation information:\n")
        # self.textBrowser_sim_info.append("%-4s %-8s  %-6s  %-32s %-56s %-52s %-7s %-4s" %
        #                                  ("step", "time", "sim", "flow", "speed", "queue", "delay", "act"))
        # self.textBrowser_sim_info.append("*" * 176)

    # 刷新仿真信息文本浏览框
    def update_simulation_textBrowser(self, info):
        self.textBrowser_sim_info.append(info)
        # 保存仿真信息文本
        # with open('./model/simulation.txt', 'a+') as f:
        #     f.write(info + '\n')
        QApplication.processEvents()

    # 刷新进度条
    def update_simulation_progressBar(self, progress):
        # 仿真进度
        simulation_percent = progress
        self.progressBar_simulation.setValue(simulation_percent)
        self.progressBar_simulation.update()

    # 刷新仿真剩余时间
    def update_simulation_time(self, h, m, s):
        # 剩余时间
        info = '剩余时间：%02d时%02d分%02d秒' % (h, m, s)
        self.label_remain_time.setText(info)
        self.label_remain_time.setFont(QFont("SimSun-ExtB", 11))

    # 保存仿真数据记录
    def save_simulation_data(self, info):
        if info:
            self.pushButton_simulation_stop_continue.setStyleSheet(self.QSS_sheet[1])
            self.pushButton_simulation_stop_continue.setText("停止仿真")
            # 保存为txt文件
            name = time.strftime("%Y-%m-%d-%H-%M-%S") + ".txt"
            path = "./result/simulation.txt"
            with open(path, "w") as f:
                f.write(self.textBrowser_sim_info.toPlainText())
            path = "./result/evaluate.txt"
            with open(path, "w") as f:
                f.write(self.textBrowser_eva_info.toPlainText())
            # 绘制延误曲线
            delay = []
            legend = ["delay"]
            with open(path, "r") as f:
                lines = f.readlines()
            for i in range(4 + 5, len(lines)):
                line = lines[i].split()[-1][1:-1]
                delay.append(float(line))
            # 绘图
            x = np.linspace(0, len(delay), len(delay))
            plt.plot(x, delay)
            # 设置坐标轴名称
            plt.xlabel("Step", fontproperties='Times New Roman', size=10.5)
            plt.ylabel("delay", fontproperties='Times New Roman', size=10.5)
            # 设置网格
            plt.grid()
            # 设置图例
            plt.legend(legend, loc="lower right", frameon=False)
            # 设置标题
            plt.title("Average delay variation", fontproperties='SimSun', size=10.5)
            # 保存图片
            plt.savefig("delay.svg")

    # 检测vissim是否运行
    @staticmethod
    def detect_vissim():
        try:
            pids = psutil.pids()
            for pid in pids:
                p = psutil.Process(pid)
                process_name = p.name()
                if "vissim" in process_name:
                    return True
            else:
                return False
        except:
            return False

    # 运行仿真
    def run_simulation(self):
        # 启动vissim
        start = time.perf_counter()
        project_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        command = r'%s\RunAsDate.exe /movetime %s "%s"' % (project_path, self.start_time, self.program_path)
        os.system(command)
        # 检测vissim是否运行
        flag = self.detect_vissim()
        if not flag:
            reply = QMessageBox.warning(self, "警告", "请先运行Vissim 4.30 软件",
                                        QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
            if reply == QMessageBox.Yes:
                self.waiting.show()
                try:
                    command = r'%s\RunAsDate.exe /movetime %s "%s"' % (project_path, self.start_time, self.program_path)
                    os.system(command)
                    while True:
                        flag = self.detect_vissim()
                        if flag:
                            for i in range(80000):
                                QApplication.processEvents()
                            start_flag = True
                            self.waiting.close()
                            break
                except (IndexError, Exception):
                    start_flag = False
                    self.waiting.close()
                self.waiting.close()
            else:
                start_flag = False
        else:
            start_flag = True
        end = time.perf_counter()
        print("Vissim仿真启动时间：%.2f" % (end - start))
        if start_flag:
            if simulation.device == "cuda":
                reply = QMessageBox.information(self, "提示", "系统检测到GPU,是否选择GPU进行训练",
                                                QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
                if reply == QMessageBox.No:
                    simulation.device = "cpu"
            self.pushButton_simulation_run.setEnabled(False)
            self.pushButton_simulation_stop_continue.setEnabled(True)
            self.pushButton_simulation_quit.setEnabled(True)
            self.pushButton_simulation_run.setStyleSheet(self.QSS_sheet[1])
            self.pushButton_simulation_stop_continue.setStyleSheet(self.QSS_sheet[0])
            self.pushButton_simulation_quit.setStyleSheet(self.QSS_sheet[0])
            # 备份已存在的网络模型文件
            self.backup_model('./model/', './backup/')
            # 显示提示信息
            self.show_hint_information()
            # 加载Vissim仿真环境参数
            # self.load_environment_setting()
            # 加载仿真配时方案参数设置
            # self.load_timing_setting()
            # 策略设置
            # self.load_strategy_setting()
            # 奖励设置
            # self.load_reward_setting()
            # 加载仿真算法参数
            self.load_algorithm_setting()
            # 加载仿真训练参数
            self.load_train_setting()
            # 加载网络模型参数设置
            # self.load_network_setting()
            # 加载车流量配置文件
            # self.load_traffic_flow_file()
            # 加载仿真运行模式
            # self.simulation_run_model_setting()
            # 加载仿真提示信息类型
            # self.simulation_hint_type_setting()
            # 生成配时方案
            plans_file = "./model/txt/test_fix_plans.txt"
            self.plans, _ = self.create_plans(self.timming, plans_file, 20)
            # plans = read_plans(plan_file)
            # 启动仿真
            self.sim_task.stop_flag = False
            self.sim_task.para = [self.net_path, self.simulation, self.plans]
            self.sim_task.start()
            end = time.perf_counter()
            print("Vissim仿真运行时间：%.2f" % (end - start))

    # 停止/继续仿真
    def stop_continue_simulation(self):
        if self.pushButton_simulation_stop_continue.text() == "停止仿真":
            self.pushButton_simulation_stop_continue.setText("继续仿真")
            self.pushButton_simulation_quit.setEnabled(True)
            self.pushButton_simulation_quit.setStyleSheet(self.QSS_sheet[0])
            if self.sim_task.handle == -1:
                return 0
            SuspendThread(self.sim_task.handle)
            info = "Stop of the Simulation"
            self.textBrowser_sim_info.append(info)
        elif self.pushButton_simulation_stop_continue.text() == "继续仿真":
            self.pushButton_simulation_stop_continue.setText("停止仿真")
            self.pushButton_simulation_quit.setEnabled(False)
            self.pushButton_simulation_quit.setStyleSheet(self.QSS_sheet[1])
            if self.sim_task.handle == -1:
                return 0
            ResumeThread(self.sim_task.handle)
            info = "Continue of the Simulation"
            self.textBrowser_sim_info.append(info)

    # 结束仿真
    def quit_simulation(self):
        # 销毁子线程服务
        ctypes.windll.kernel32.TerminateThread(self.sim_task.handle, 0)
        os.system("taskkill /F /IM vissim.exe")
        self.sim_task.stop_flag = True
        self.sim_task.stop_flag = True
        self.sim.EndInfoEmit.emit(True)
        self.pushButton_simulation_quit.setEnabled(False)
        self.pushButton_simulation_run.setEnabled(True)
        self.pushButton_simulation_stop_continue.setEnabled(False)
        self.pushButton_simulation_run.setStyleSheet(self.QSS_sheet[0])
        self.pushButton_simulation_quit.setStyleSheet(self.QSS_sheet[1])
        self.pushButton_simulation_stop_continue.setStyleSheet(self.QSS_sheet[1])
        self.pushButton_simulation_stop_continue.setText("停止仿真")
        # 重新注册线程
        self.sim_task = MyThread()
        self.sim_task.finished.connect(self.sim_task.deleteLater)
        # 连接自定义信号
        self.sim = self.sim_task.sim
        self.sim.SimInfoEmit.connect(self.update_simulation_textBrowser)
        self.sim.EvaInfoEmit.connect(self.update_evaluate_textBrowser)
        # 结束仿真
        info = "End of the Simulation"
        self.textBrowser_sim_info.append(info)

    # 关闭事件
    def closeEvent(self, event):
        if self.sim_task.isRunning():
            self.sim_task.quit()
            self.sim_task.terminate()
        del self.sim_task
        super(MainWindow, self).closeEvent(event)

    """评估信息界面"""

    # 刷新可视化界面
    def update_figure(self):
        try:
            self.F1.plot_drl_train(self.pic1)
            self.F2.plot_drl_test(self.pic2)
            self.F3.plot_fix_test(self.pic3)
        except:
            QMessageBox.warning(self, "警告", "读取数据文件异常")

    # 选择训练深度强化学习可视化展示图像
    def select_train_drl_display(self):
        self.pic1 = [0 for _ in range(8)]
        if self.checkBox_train_delay.isChecked():
            self.pic1[0] = 1
        if self.checkBox_train_reward.isChecked():
            self.pic1[1] = 1
        if self.checkBox_train_loss.isChecked():
            self.pic1[2] = 1
        if self.checkBox_train_epsilon.isChecked():
            self.pic1[3] = 1
        if self.checkBox_train_lr.isChecked():
            self.pic1[4] = 1
        if self.checkBox_train_conv.isChecked():
            self.pic1[5] = 1
        if self.checkBox_train_test_delay.isChecked():
            self.pic1[6] = 1
        if self.checkBox_train_test_reward.isChecked():
            self.pic1[7] = 1
        # print(self.pic1)
        try:
            self.F1.plot_drl_train(self.pic1)
        except:
            QMessageBox.warning(self, "警告", "读取深度强化配时训练数据文件异常")

    # 选择训练深度强化学习可视化展示图像
    def select_test_drl_display(self):
        self.pic2 = [0 for _ in range(6)]
        if self.checkBox_test_delay.isChecked():
            self.pic2[0] = 1
        if self.checkBox_delay_box.isChecked():
            self.pic2[1] = 1
        if self.checkBox_plan_scatter.isChecked():
            self.pic2[2] = 1
        if self.checkBox_plan_freq.isChecked():
            self.pic2[3] = 1
        if self.checkBox_compare_all_fix.isChecked():
            self.pic2[4] = 1
        if self.checkBox_compare_specify_fix.isChecked():
            plan = self.spinBox_plan.value()
            self.pic2[5] = plan
        # print(self.pic2)
        try:
            self.F2.plot_drl_test(self.pic2)
        except:
            QMessageBox.warning(self, "警告", "读取深度强化配时测试数据文件异常")

    # 选择训练深度强化学习可视化展示图像
    def select_test_fix_display(self):
        self.pic3 = [0 for _ in range(3)]
        if self.checkBox_fix_all_test_delay.isChecked():
            self.pic3[0] = 1
        if self.checkBox_fix_min_test_delay.isChecked():
            self.pic3[1] = 1
        if self.checkBox_specify_fix.isChecked():
            plan = self.spinBox_plan_fix.value()
            self.pic3[2] = plan
        # print(self.pic3)
        try:
            self.F3.plot_fix_test(self.pic3)
        except:
            QMessageBox.warning(self, "警告", "读取固定配时测试数据文件异常")
